//+------------------------------------------------------------------+
//|             RFEA_Gold_TrendDCA_Solo.mq5                          |
//|  单文件EA：内置RangeFilter趋势判定 + 同向DCA加仓机制             |
//|  无需 #include，无需 iCustom                                     |
//+------------------------------------------------------------------+

/* ==============================
   策略概要：
   - 使用内置 Range Filter 判断趋势（收盘K线判定上升=多，下降=空）
   - 仅在当前趋势方向开仓/加仓；反向趋势冻结旧单
   - 网格距离：ATR×系数 或 固定美元间距
   - 达到账户总盈利(USD) → 全平；达到回撤阈值 → 暂停开仓/加仓
   - 仅在新K线处理逻辑，避免重绘与抖动
   ============================== */

// ---------------- 1) 输入参数 ----------------
input int    Inp_Range_Period       = 14;        // RangeFilter周期
input double Inp_Range_Multiplier   = 3.0;       // Swing倍数
input ENUM_APPLIED_PRICE Inp_Range_Source = PRICE_CLOSE;

input bool   Inp_Use_ATR_Grid       = true;      // 是否使用ATR动态网格
input int    Inp_ATR_Period         = 14;        // ATR周期
input double Inp_ATR_Grid_Mult      = 0.80;      // Grid = ATR * Mult (美元)
input double Inp_Fixed_Grid_USD     = 3.00;      // 固定网格（美元）

input int    Inp_Max_Orders         = 6;         // 单方向最大加仓层数
input double Inp_Base_Lots          = 0.10;      // 首单手数
input double Inp_Martin_Multiplier  = 1.40;      // 马丁倍数

input double Inp_TargetProfit_USD   = 100.0;     // 达标盈利(USD)

input double Inp_ProtectiveSL_ATR   = 0.0;       // 保护性止损倍数(ATR×倍数，0=关闭)

input double Inp_MaxDrawdownPct     = 25.0;      // 回撤阈值(%)

input bool   Inp_Use_Session        = true;      // 是否启用交易时段过滤
input int    Inp_TradeStartHour     = 7;
input int    Inp_TradeEndHour       = 18;

input int    Inp_SlippagePoints     = 30;        // 滑点(点)
input int    Inp_Magic              = 777001;    // 魔术号
input bool   Inp_Enable_Alerts      = true;      // 启用提示音

// ---------------- 2) 全局变量 ----------------
enum TrendStateEnum { TS_None=0, TS_Long=1, TS_Short=-1 };
int       g_trend         = TS_Long;             // 当前趋势状态
datetime  g_last_bar_time = 0;                   // 最近一根bar时间

int       g_handle_atr    = INVALID_HANDLE;      // ATR句柄

// ---- Range Filter 内部状态 ----
bool   g_rf_seeded   = false;   // 是否已预热
double g_ema1        = 0.0;     // EMA(abs(diff), n)
double g_ema2        = 0.0;     // EMA(ema1, wper)
double g_filt        = 0.0;     // 当前滤波值
double g_filt_prev   = 0.0;     // 上一根滤波值
int    g_dir_prev    = 0;       // 上一根方向（-1/0/1）
double g_prev_src    = 0.0;     // 上一根已收盘价

// ---------------- 3) 工具函数 -----------------
double GetAppliedPrice(const int shift)
{
   double o=iOpen(_Symbol,PERIOD_CURRENT,shift);
   double h=iHigh(_Symbol,PERIOD_CURRENT,shift);
   double l=iLow(_Symbol,PERIOD_CURRENT,shift);
   double c=iClose(_Symbol,PERIOD_CURRENT,shift);

   switch(Inp_Range_Source)
   {
      case PRICE_OPEN:    return o;
      case PRICE_HIGH:    return h;
      case PRICE_LOW:     return l;
      case PRICE_MEDIAN:  return (h+l)*0.5;
      case PRICE_TYPICAL: return (h+l+c)/3.0;
      case PRICE_WEIGHTED:return (h+l+c+c)/4.0;
      default:            return c; // PRICE_CLOSE
   }
}


//+------------------------------------------------------------------+
//| 获取支持的填充模式                                                |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING GetSupportedFillingMode()
{
    long filling_mode = SymbolInfoInteger(Symbol(), SYMBOL_FILLING_MODE);
    
    if((filling_mode & SYMBOL_FILLING_IOC) != 0)
        return ORDER_FILLING_IOC;
    else if((filling_mode & SYMBOL_FILLING_FOK) != 0)
        return ORDER_FILLING_FOK;
    else
        return ORDER_FILLING_IOC; // 默认使用IOC
}

// 判断是否新K线
bool IsNewBar()
{
   datetime t0 = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(t0 != g_last_bar_time){ g_last_bar_time=t0; return true; }
   return false;
}

// 手数归一化
double NormalizeLots(double lots)
{
   double minlot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxlot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double step   = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   lots = MathMax(minlot, MathMin(maxlot, lots));
   double steps = MathRound(lots/step);
   return steps*step;
}

// 判断交易时段是否允许
bool AllowTradingWindow()
{
   return true; 
   if(!Inp_Use_Session) return true;
   MqlDateTime t; datetime now=TimeCurrent(); TimeToStruct(now, t);
   int h=t.hour;
   if(Inp_TradeStartHour<=Inp_TradeEndHour)
      return (h>=Inp_TradeStartHour && h<Inp_TradeEndHour);
   // 若跨夜时段
   return (h>=Inp_TradeStartHour || h<Inp_TradeEndHour);
}

// 风险控制：检查回撤是否超限
bool RiskOK()
{
   double bal = AccountInfoDouble(ACCOUNT_BALANCE);
   double eq  = AccountInfoDouble(ACCOUNT_EQUITY);
   if(bal<=0.0) return false;
   double ddPct = (1.0 - eq/bal)*100.0;
   if(ddPct >= Inp_MaxDrawdownPct)
   {
      Print("⚠️暂停交易：回撤 ", DoubleToString(ddPct,2), "% ≥ ", DoubleToString(Inp_MaxDrawdownPct,2), "%");
      if(Inp_Enable_Alerts) Alert("⚠️暂停交易：回撤 ", DoubleToString(ddPct,2), "% ≥ ", DoubleToString(Inp_MaxDrawdownPct,2), "%");
      return false;
   }
   return true;
}

// 获取ATR值
double GetATR()
{
   if(g_handle_atr==INVALID_HANDLE) return 0.0;
   double buf[];
   if(CopyBuffer(g_handle_atr, 0, 0, 2, buf) < 1) return 0.0;
   return buf[0];
}

// 计算网格间距
double GetGridStep()
{
   if(Inp_Use_ATR_Grid)
   {
      double atr = GetATR();
      double min_step = SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 10.0; // 防止过小
      return MathMax(atr * Inp_ATR_Grid_Mult, min_step);
   }
   return Inp_Fixed_Grid_USD;
}

// ---------------- 4) Range Filter算法 ----------------
double EmaUpdate(double prev_ema, double value, int period)
{
   if(period<=1) return value;
   double alpha = 2.0/(period+1.0);
   return alpha*value + (1.0-alpha)*prev_ema;
}

// RangeFilter预热：从历史K线恢复内部状态
void RF_Seed()
{
   int bars = MathMin(1000, Bars(_Symbol, PERIOD_CURRENT));
   if(bars<5) return;

   int idx;
   for(idx=bars-1; idx>=1; --idx) // 从老到新（不含当前未收盘）
   {
      double x  = GetAppliedPrice(idx);
      double xp = GetAppliedPrice(idx+1);
      double diff = MathAbs(x - xp);

      g_ema1 = (g_rf_seeded ? EmaUpdate(g_ema1, diff, Inp_Range_Period) : diff);
      int wper = (Inp_Range_Period*2) - 1;
      g_ema2 = (g_rf_seeded ? EmaUpdate(g_ema2, g_ema1, wper) : g_ema1);
      double rng = g_ema2 * Inp_Range_Multiplier;

      if(!g_rf_seeded){ g_filt=x; g_filt_prev=g_filt; g_dir_prev=0; g_rf_seeded=true; }

      if(x - rng >= g_filt) g_filt = x - rng;
      if(x + rng <= g_filt) g_filt = x + rng;

      g_prev_src  = x;
      g_filt_prev = g_filt;
   }
}

// 每根收盘K线更新并返回方向（-1/0/1）
int RF_Step_GetDir()
{
   if(!g_rf_seeded) RF_Seed();

   double x = GetAppliedPrice(1); // 已收盘K线
   double diff = MathAbs(x - g_prev_src);

   g_ema1 = EmaUpdate(g_ema1, diff, Inp_Range_Period);
   int wper = (Inp_Range_Period*2) - 1;
   g_ema2 = EmaUpdate(g_ema2, g_ema1, wper);
   double rng = g_ema2 * Inp_Range_Multiplier;

   if(x - rng >= g_filt) g_filt = x - rng;
   if(x + rng <= g_filt) g_filt = x + rng;

   int dir = 0;
   if(g_filt > g_filt_prev) dir = 1;
   else if(g_filt < g_filt_prev) dir = -1;
   else dir = g_dir_prev;

   g_prev_src  = x;
   g_filt_prev = g_filt;
   g_dir_prev  = dir;
   return dir;
}

// ---------------- 5) 持仓管理函数 ----------------
int PosCountByType(const int type)
{
   int total = 0;
   int n = (int)PositionsTotal();
   int idx;
   ulong ticket;
   for(idx=0; idx<n; ++idx)
   {
      ticket = PositionGetTicket(idx);
      if(ticket==0) continue;
      if(!PositionSelectByTicket(ticket)) continue;
      string sym="";
      PositionGetString(POSITION_SYMBOL, sym);
      long magic = (long)PositionGetInteger(POSITION_MAGIC);
      if(sym!=_Symbol || magic!=Inp_Magic) continue;
      long pos_type = (long)PositionGetInteger(POSITION_TYPE);
      if((int)pos_type == type) ++total;
   }
   return total;
}

// 获取最后一单的开仓价
double PosLastOpenPrice(const int type)
{
   datetime last_time = 0;
   double   last_price = 0.0;
   int n = (int)PositionsTotal();
   int idx;
   ulong ticket;
   for(idx=0; idx<n; ++idx)
   {
      ticket = PositionGetTicket(idx);
      if(ticket==0) continue;
      if(!PositionSelectByTicket(ticket)) continue;
      string sym="";
      PositionGetString(POSITION_SYMBOL, sym);
      long magic = (long)PositionGetInteger(POSITION_MAGIC);
      if(sym!=_Symbol || magic!=Inp_Magic) continue;
      long pos_type = (long)PositionGetInteger(POSITION_TYPE);
      if((int)pos_type!=type) continue;
      datetime open_time = (datetime)PositionGetInteger(POSITION_TIME);
      double   open_price = PositionGetDouble(POSITION_PRICE_OPEN);
      if(open_time > last_time){ last_time = open_time; last_price = open_price; }
   }
   return last_price;
}

// 计算当前总盈利(USD)
double TotalProfitUSD()
{
   double profit = 0.0;
   int n = (int)PositionsTotal();
   int idx;
   ulong ticket;
   for(idx=0; idx<n; ++idx)
   {
      ticket = PositionGetTicket(idx);
      if(ticket==0) continue;
      if(!PositionSelectByTicket(ticket)) continue;
      string sym="";
      PositionGetString(POSITION_SYMBOL, sym);
      long magic = (long)PositionGetInteger(POSITION_MAGIC);
      if(sym!=_Symbol || magic!=Inp_Magic) continue;
      profit += PositionGetDouble(POSITION_PROFIT);
   }
   return profit;
}

// 计算下一单手数（马丁倍数）
double NextLotSize(const int type)
{
   int levels = PosCountByType(type);
   double lots = Inp_Base_Lots;
   int level;
   for(level=1; level<levels; ++level) lots *= Inp_Martin_Multiplier;
   return NormalizeLots(lots);
}

// 市价下单
bool OpenMarketf(const int type, const double lots, const double sl=0.0, const double tp=0.0)
{
   MqlTradeRequest req;  ZeroMemory(req);
   MqlTradeResult  res;  ZeroMemory(res);
   req.action    = TRADE_ACTION_DEAL;
   req.symbol    = _Symbol;
   req.magic     = Inp_Magic;
   req.volume    = lots;
   req.deviation = Inp_SlippagePoints;
   if(type==ORDER_TYPE_BUY){ req.type=ORDER_TYPE_BUY; req.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK); }
   else                    { req.type=ORDER_TYPE_SELL; req.price=SymbolInfoDouble(_Symbol,SYMBOL_BID); }
   req.sl = sl; req.tp = tp;
   ResetLastError();
   req.type_filling = GetSupportedFillingMode();
   
   bool ok = OrderSend(req, res);
   if(!ok) PrintFormat("OrderSend失败: %d (%s)", GetLastError(), res.comment);
   return ok;
}



//+------------------------------------------------------------------+
//| 执行市价订单                                                     |
//| 参数:                                                            |
//|   type  - 订单类型(ORDER_TYPE_BUY 或 ORDER_TYPE_SELL)            |
//|   lots  - 交易手数                                               |
//|   sl    - 止损价格(0表示无止损)                                  |
//|   tp    - 止盈价格(0表示无止盈)                                  |
//| 返回值:                                                          |
//|   true  - 订单成功发送                                           |
//|   false - 订单发送失败                                           |
//+------------------------------------------------------------------+
bool OpenMarket(const ENUM_ORDER_TYPE type, const double lots, const double sl=0.0, const double tp=0.0)
{
   // 检查交易品种是否允许交易 
   if(!SymbolInfoInteger(_Symbol, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_FULL)
   {
      PrintFormat("交易品种 %s 不允许交易", _Symbol);
      return false;
   }
   
   // 检查手数是否有效 
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   
   if(lots < minLot || lots > maxLot)
   {
      PrintFormat("无效手数: %.2f (允许范围: %.2f-%.2f)", lots, minLot, maxLot);
      return false;
   }
   
   // 标准化手数 
   double normalizedLot = NormalizeDouble(lots / lotStep, 0) * lotStep;
   if(normalizedLot != lots)
   {
      PrintFormat("手数 %.2f 已调整为 %.2f (步长: %.2f)", lots, normalizedLot, lotStep);
   }
   
   // 设置交易请求 
   MqlTradeRequest req;
   ZeroMemory(req);
   MqlTradeResult res;
   ZeroMemory(res);
   
   req.action  = TRADE_ACTION_DEAL;
   req.symbol  = _Symbol;
   req.magic  = Inp_Magic;
   req.volume  = normalizedLot;
   req.deviation  = Inp_SlippagePoints;

   req.type_filling = GetSupportedFillingMode();
   // 设置价格 
   if(type == ORDER_TYPE_BUY)
   {
      req.type  = ORDER_TYPE_BUY;
      req.price  = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   }
   else 
   {
      req.type  = ORDER_TYPE_SELL;
      req.price  = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   }
   
   // 设置止损止盈 
   if(sl > 0) req.sl  = sl;
   if(tp > 0) req.tp  = tp;
   
   // 发送订单 
   ResetLastError();
   bool ok = OrderSend(req, res);
   
   if(!ok)
   {
      PrintFormat("OrderSend失败: %d (%s)", GetLastError(), res.comment); 
      PrintFormat("请求: type=%d, price=%.5f, volume=%.2f, sl=%.5f, tp=%.5f",
                 req.type,  req.price,  req.volume,  req.sl,  req.tp); 
      PrintFormat("结果: retcode=%d, deal=%I64u, order=%I64u", res.retcode,  res.deal,  res.order); 
   }
   else 
   {
      PrintFormat("订单成功: deal=%I64u, order=%I64u, price=%.5f", res.deal,  res.order,  res.price); 
   }
   
   return ok;
}
// 全部平仓
bool CloseAll()
{
   bool ok_all = true;
   int idx;
   ulong ticket;
   for(idx=PositionsTotal()-1; idx>=0; --idx)
   {
      ticket = PositionGetTicket(idx);
      if(ticket==0) continue;
      if(!PositionSelectByTicket(ticket)) continue;
      string sym="";
      PositionGetString(POSITION_SYMBOL, sym);
      long magic = (long)PositionGetInteger(POSITION_MAGIC);
      if(sym!=_Symbol || magic!=Inp_Magic) continue;
      long pos_type = (long)PositionGetInteger(POSITION_TYPE);
      double lots = PositionGetDouble(POSITION_VOLUME);
      double price = (pos_type==POSITION_TYPE_BUY)
                     ? SymbolInfoDouble(_Symbol, SYMBOL_BID)
                     : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      MqlTradeRequest r; ZeroMemory(r);
      MqlTradeResult  s; ZeroMemory(s);
      r.action    = TRADE_ACTION_DEAL;
      r.symbol    = _Symbol;
      r.magic     = Inp_Magic;
      r.volume    = lots;
      r.deviation = Inp_SlippagePoints;
      r.type      = (pos_type==POSITION_TYPE_BUY) ? ORDER_TYPE_SELL : ORDER_TYPE_BUY;
      r.price     = price;
      r.position  = ticket;
      r.type_filling = GetSupportedFillingMode();
      if(!OrderSend(r,s))
      {
         ok_all = false;
         PrintFormat("Close失败: %d (%s)", GetLastError(), s.comment);
      }
   }
   return ok_all;
}

// 构建保护性止损
bool BuildProtectiveSL(const int type, double &sl_out)
{
   sl_out=0.0;
   if(Inp_ProtectiveSL_ATR<=0.0) return false;
   double atr=GetATR(); if(atr<=0.0) return false;

   if(type==ORDER_TYPE_BUY)  sl_out = SymbolInfoDouble(_Symbol,SYMBOL_BID) - atr*Inp_ProtectiveSL_ATR;
   else                      sl_out = SymbolInfoDouble(_Symbol,SYMBOL_ASK) + atr*Inp_ProtectiveSL_ATR;
   return true;
}

// ---------------- 6) DCA加仓逻辑 ----------------
void ManageLongSeries()
{
   int buys = PosCountByType(ORDER_TYPE_BUY);
   double grid = GetGridStep();

   // 如果没有多单 → 开第一单
   if(buys==0)
   {
      double sl=0.0; BuildProtectiveSL(ORDER_TYPE_BUY, sl);
      OpenMarket(ORDER_TYPE_BUY, NormalizeLots(Inp_Base_Lots), sl, 0.0);
      return;
   }

   // 若价格逆向偏离达到网格 → 加仓
   double last = PosLastOpenPrice(ORDER_TYPE_BUY);
   double bid  = SymbolInfoDouble(_Symbol, SYMBOL_BID);
   if(bid <= last - grid && buys < Inp_Max_Orders)
   {
      double lots = NextLotSize(ORDER_TYPE_BUY);
      double sl=0.0; BuildProtectiveSL(ORDER_TYPE_BUY, sl);
      OpenMarket(ORDER_TYPE_BUY, lots, sl, 0.0);
   }
}

void ManageShortSeries()
{
   int sells = PosCountByType(ORDER_TYPE_SELL);
   double grid = GetGridStep();

   // 如果没有空单 → 开第一单
   if(sells==0)
   {
      double sl=0.0; BuildProtectiveSL(ORDER_TYPE_SELL, sl);
      OpenMarket(ORDER_TYPE_SELL, NormalizeLots(Inp_Base_Lots), sl, 0.0);
      return;
   }

   // 若价格逆向偏离达到网格 → 加仓
   double last = PosLastOpenPrice(ORDER_TYPE_SELL);
   double ask  = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
   if(ask >= last + grid && sells < Inp_Max_Orders)
   {
      double lots = NextLotSize(ORDER_TYPE_SELL);
      double sl=0.0; BuildProtectiveSL(ORDER_TYPE_SELL, sl);
      OpenMarket(ORDER_TYPE_SELL, lots, sl, 0.0);
   }
}

// ---------------- 7) 系统回调 ----------------
int OnInit()
{
   // 初始化ATR句柄
   g_handle_atr = iATR(_Symbol, PERIOD_CURRENT, Inp_ATR_Period);
   if(g_handle_atr==INVALID_HANDLE)
   {
      Print("iATR初始化失败");
      return(INIT_FAILED);
   }

   // 记录当前bar时间；等待第一根新K线时再预热RangeFilter
   g_last_bar_time = iTime(_Symbol, PERIOD_CURRENT, 0);

   // 重置RangeFilter状态
   g_rf_seeded=false;
   g_ema1=0.0; g_ema2=0.0;
   g_filt=0.0; g_filt_prev=0.0;
   g_dir_prev=0; g_prev_src=0.0;
   
   Print("OnInit success"); 
   
   Print("经纪商支持的填充模式:");
   long filling_mode = SymbolInfoInteger(Symbol(), SYMBOL_FILLING_MODE);
   if((filling_mode & SYMBOL_FILLING_IOC) != 0)
     Print("- IOC (Immediate Or Cancel)");
   if((filling_mode & SYMBOL_FILLING_FOK) != 0)
     Print("- FOK (Fill Or Kill)");
        
        
   
   Print("使用填充模式: ", EnumToString(GetSupportedFillingMode()));
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason)
{
   // 这里无需释放指标句柄（平台会托管）；如需手动：
   // if(g_handle_atr!=INVALID_HANDLE) IndicatorRelease(g_handle_atr);
}

void OnTick()
{
   //--- Print("OnTick");
   // 交易时段与风控检查
   if(!AllowTradingWindow())
   {
      Print("交易时段与风控检查 未通过！");
      return;
   } 
   //Print("交易时段与风控检查 --通过--");
   if(!RiskOK())
   {
      Print("RiskOK 未通过！");
      return;
   } 
    //Print("RiskOK --通过--");
   // 账户总盈利达标 → 全部平仓并提示
   if(TotalProfitUSD() >= Inp_TargetProfit_USD)
   {
      if(CloseAll() && Inp_Enable_Alerts) Alert("✅ 达标盈利，已全部平仓。");
      Print("✅ 达标盈利，已全部平仓。");
      return;
   }

   // 仅在新K线执行信号/加仓逻辑
   if(!IsNewBar())
   {
      // Print("IsNewBar 未通过！ 仅在新K线执行信号/加仓逻辑");
      return;  
   }  
    Print("IsNewBar 通过！");
    
   // 第一次新K线先预热RangeFilter
   if(!g_rf_seeded)
   {
    Print("第一次新K线先预热RangeFilter！");
    RF_Seed(); 
    g_rf_seeded = true; 
    return;
     
   }

   // RangeFilter 更新并获取方向
   int dir = RF_Step_GetDir();
   if(dir>0)      g_trend = TS_Long;
   else if(dir<0) g_trend = TS_Short; // dir==0 保持原趋势

   Print("方向：", g_trend);  

   // 只在当前趋势方向执行同向DCA；反向冻结
   if(g_trend == TS_Long)       ManageLongSeries();
   else if(g_trend == TS_Short) ManageShortSeries();
}
//+------------------------------------------------------------------+
